package iso;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;

public class Executer {

	private final String cmd;
	private final Process child;
	private final Thread stdoutReader;
	private final Thread stderrReader;

	public Executer(String cmd) throws IOException {
		this(cmd, null, null);
	}

	public Executer(String[] cmdarray) throws IOException {
		this(cmdarray, null, null);
	}

	public Executer(String cmd, OutputStream stdout, OutputStream stderr)
			throws IOException {
		this(tokenize(cmd), stdout, stderr);
	}

	public Executer(String[] cmdarray, OutputStream stdout, OutputStream stderr)
			throws IOException {
		this.cmd = toString(cmdarray, ' ');
		System.out.println("=>invoke: " + cmd);
		// if (log.isDebugEnabled()) log.debug("invoke: " + cmd);
		this.child = Runtime.getRuntime().exec(cmdarray);

		this.stdoutReader = startCopy(child.getInputStream(), stdout);
		this.stderrReader = startCopy(child.getErrorStream(), stderr);
	}

	private static String[] tokenize(String cmd) {
		ArrayList<String> cmdarray = new ArrayList<String>();
		final int len = cmd.length();
		char[] c = new char[len + 1];
		cmd.getChars(0, len, c, 0);
		c[len] = ' ';
		char delim = '\0';
		for (int i = 0, off = 0; i <= len; ++i) {
			if (delim == '\0') {
				if (c[i] == ' ') {
					continue;
				}
				if (c[i] == '"') {
					delim = '"';
					off = i + 1;
				} else {
					delim = ' ';
					off = i;
				}
			} else {
				if (c[i] == delim) {
					int count = i - off;
					if (count > 0) {
						cmdarray.add(new String(c, off, count));
					}
					delim = '\0';
				}
			}
		}
		return (String[]) cmdarray.toArray(new String[cmdarray.size()]);
	}

	public static String toString(String[] a, char delim) {
		if (a == null) {
			return null;
		}
		if (a.length == 0) {
			return "";
		}
		if (a.length == 1) {
			return a[0];
		}
		StringBuffer sb = new StringBuffer(a[0]);
		for (int i = 1; i < a.length; ++i) {
			sb.append(delim).append(a[i]);
		}
		return sb.toString();
	}

	public final String cmd() {
		return cmd;
	}

	public int waitFor() throws InterruptedException {
		stdoutReader.join();
		stderrReader.join();
		int exit = child.waitFor();

		System.out.println("=>exit(" + exit + "): " + cmd);

		return exit;
	}

	public void destroy() {
		child.destroy();
	}

	private Thread startCopy(final InputStream in, final OutputStream out) {
		Thread t = new Thread(new Runnable() {
			public void run() {
				BufferedReader br = new BufferedReader(
						new InputStreamReader(in));
				try {
					String lineB = null;
					while ((lineB = br.readLine()) != null) {
						if (lineB != null) {
							System.out.println(">" + lineB);
						}
					}

					// int len;
					// byte[] buf = new byte[512];
					// while ((len = in.read(buf)) != -1) {
					// if (out != null) {
					// out.write(buf, 0, len);
					// }
					// }
				} catch (IOException e) {
					System.out.println("i/o error reading stdout/stderr of "
							+ cmd + "\n" + e);
					// log.warn("i/o error reading stdout/stderr of " + cmd, e);
				} finally {
					try {
						in.close();
					} catch (IOException e) {
						System.out
								.println("i/o exception on close of stdout/stderr of "
										+ cmd + "\n" + e);
						// log.warn("i/o exception on close of stdout/stderr of "
						// + cmd, e);
					}
				}
			}
		});
		t.start();
		return t;
	}
}